﻿using Microsoft.Extensions.Logging;
using Polly;
using Polly.Timeout;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace Odyssey.Http.Implementations
{
    public class PollyHttpRequest : IPollyHttpRequester
    {
        private readonly IHttpRequester _httpRequester;
        private readonly IHttpConfiguration _httpConfiguration;
        private readonly ILogger _logger;

        public PollyHttpRequest(IHttpRequester httpRequester, IHttpConfiguration httpConfiguration, ILogger<PollyHttpRequest> logger)
        {
            _httpRequester = httpRequester;
            _httpConfiguration = httpConfiguration;
            _logger = logger;
        }

        public async Task<T> RetryAsync<T>(Func<IHttpRequester, Task<T>> requestExecuter, int retryCount, Func<HttpRequestException, int, Task> onRetry)
        {
            var retryPolicy = BuildRetryPolicy(retryCount, onRetry);
            return await retryPolicy.ExecuteAsync(async () =>
            {
                return await requestExecuter.Invoke(_httpRequester);
            });
        }

        public Policy BuildRetryPolicy(int retryCount, Func<HttpRequestException, int, Task> onRetry)
        {
            return Policy.Handle<HttpRequestException>().RetryAsync(retryCount, async (exception, count) =>
             {
                 await onRetry.Invoke(exception as HttpRequestException, count);
             });
        }

        public async Task<T> WaitAndRetryAsync<T>(Func<IHttpRequester, Task<T>> requestExecuter, IEnumerable<TimeSpan> sleepDurations, Func<Exception, TimeSpan, Task> onWaitAndRetry)
        {
            var waitAndRetryPolicy = BuildWaitAndRetryPolicy(sleepDurations, onWaitAndRetry);
            return await waitAndRetryPolicy.ExecuteAsync(async () =>
            {
                return await requestExecuter.Invoke(_httpRequester);
            });
        }

        public Policy BuildWaitAndRetryPolicy(IEnumerable<TimeSpan> sleepDurations, Func<HttpRequestException, TimeSpan, Task> onWaitAndRetry)
        {
            return Policy
                  .Handle<HttpRequestException>()
                  .WaitAndRetryAsync(sleepDurations, async (exception, timeSpan) =>
                  {
                      await onWaitAndRetry(exception as HttpRequestException, timeSpan);
                  });
        }

        public async Task<T> TimeoutAsync<T>(Func<IHttpRequester, Task<T>> requestExecuter, Func<Task> onTimeout, int timeoutSeconds)
        {
            var timeoutPolicy = BuildTimeoutPolicy(onTimeout, timeoutSeconds);
            return await timeoutPolicy.ExecuteAsync(async () =>
            {
                return await requestExecuter.Invoke(_httpRequester);
            });
        }


        public Policy BuildTimeoutPolicy(Func<Task> onTimeout, int timeoutSeconds)
        {
            if (timeoutSeconds <= 0)
            {
                timeoutSeconds = _httpConfiguration.DefaultTimeoutSeconds;
            }
            return Policy.TimeoutAsync(timeoutSeconds, TimeoutStrategy.Pessimistic, async (context, timespan, task) =>
             {
                 await onTimeout.Invoke();
             });
        }

        public async Task<T> WrapAsync<T>(Func<IHttpRequester, Task<T>> requestExecuter, params Policy[] policies)
        {
            var warpPolicy = Policy.WrapAsync(policies);
            return await warpPolicy.ExecuteAsync(async () =>
             {
                 return await requestExecuter.Invoke(_httpRequester);
             });
        }
    }
}
